import numpy as np
import random
import copy
import matplotlib.pyplot as plt


# 工作安排类，每一个序列对应一个工作安排
class WorkAssignment:

    def __init__(self, person_list, job_list, work_cost_matrix, transport_cost_matrix, work_assign_sequence):
        # 人员列表
        self._person_list = person_list
        # 任务列表
        self._job_list = job_list
        # 工作任务消耗信息
        self._work_cost_matrix = work_cost_matrix
        # 交通时间信息
        self._transport_cost_matrix = transport_cost_matrix
        # 工作安排顺序信息
        self._work_assign_sequence = work_assign_sequence
        # 人员实体类
        self._person_entity_list = []
        # 由于不存在任务不同的阶段，所以不需要job_entity
        # self._job_entity_list = []

        # 人员实体列表，用于存储人员当前的工作状态,主要运算都在实体类操作
        personEntityList = []
        for person_id, person_info in enumerate(person_list):
            personEntity = PersonEntity(person_info, job_list, work_cost_matrix, transport_cost_matrix)
            personEntityList.append(personEntity)
        self._person_entity_list = personEntityList

    # 安排一个工作
    def eachWorkAssign(self, each_work_assign_id):
        # 遍历整个人员列表，获取其开工信息，确认哪个时间段是当前任务能安排的，以最短时长策略安排
        # 即开工时间加上本任务的工作时间在时间轴上最短
        min_time = 9999999
        select_person = None
        for index, value in enumerate(self._person_entity_list):
            person_id = value._person_info['person_id']
            # 获取该人员空闲时间,如果没任务则为0
            person_free_time = value._now_work_end_time
            # 获取该人员上一个任务的id,如果是没任务则为-1
            person_prev_job_id = value._now_job_id
            # 获取上一个任务到目前任务地址所需要的时间开销,
            # 如果没任务person_prev_job_id会是-1,正好是起点,所以不用判断
            this_transport_cost = self._transport_cost_matrix[person_prev_job_id][each_work_assign_id]
            # 获取当前人员需要做该任务的时间开销
            this_work_cost = self._work_cost_matrix[person_id][each_work_assign_id]
            # 如果person_free_time + this_transport_cost + this_work_cost小于min_time
            # 则更新选择的select_person_id和min_time,求出最应该安排的人
            if (person_free_time + this_transport_cost + this_work_cost) <= min_time:
                min_time = (person_free_time + this_transport_cost + this_work_cost)
                select_person = value
        select_person.updateMachineDoneJobStatus(each_work_assign_id)

    # 安排一个序列的工作
    def workAssign(self, work_assign_sequence):
        for index, value in enumerate(work_assign_sequence):
            self.eachWorkAssign(value)

    # 画甘特图
    def drawGantt(self):
        MColor = ['red', 'blue', 'yellow', 'orange', 'green', 'moccasin', 'purple',
                  'pink', 'navajowhite', 'Thistle', 'Magenta', 'SlateBlue', 'RoyalBlue',
                  'Aqua', 'floralwhite', 'ghostwhite', 'goldenrod', 'mediumslateblue',
                  'navajowhite', 'navy', 'sandybrown']
        fig = plt.figure(figsize=(15, 6), dpi=70)
        # 横众轴显示字体为‘仿宋’的中文标签
        plt.rcParams['font.sans-serif'] = ['FangSong']
        plt.rcParams['axes.unicode_minus'] = False
        for each_person in self._person_entity_list:
            person_id = each_person._person_info['person_id']
            person_name = each_person._person_info['person_name']
            job_done_list = each_person._job_done_list
            for each_job in job_done_list:
                # 画交通时间开销
                plt.barh(person_name, width=each_job['transport_cost_time'],
                         height=0.8,
                         color='white',
                         edgecolor='black',
                         left=each_job['prev_job_end_time'])
                # 画工作开销
                plt.barh(person_name, width=each_job['work_cost_time'],
                         height=0.8,
                         color=MColor[each_job['job_id']],
                         edgecolor='black',
                         left=each_job['start_time'])
                # 交通开销表标明文字
                plt.text(x=each_job['prev_job_end_time'] + (each_job['transport_cost_time'] / 2 - 0.25),
                         y=person_id - 0.2,
                         color='black',
                         weight='bold',
                         s=each_job['prev_job_name']+'\n'+each_job['job_name'],
                         size=8, fontproperties='SimSun')
                # 工作开销标明文字
                plt.text(x=each_job['start_time'] + (each_job['work_cost_time'] / 2 - 0.25),
                         y=person_id - 0.2,
                         color='black',
                         weight='bold',
                         s=each_job['job_name'],
                         size=12, fontproperties='SimSun')

        plt.ylabel("人员", size=20, fontproperties='SimSun')
        plt.xlabel("小时", size=20, fontproperties='SimSun')
        plt.tick_params(labelsize=20)
        plt.tick_params(direction='in')
        plt.show()

    # 计算适应度
    def calFitness(self):
        all_person_max_time = 0
        for each_person in self._person_entity_list:
            # 获取每个人最后任务的完成时间
            each_personMaxTime = 0
            if len(each_person._job_done_list) != 0:
                each_personMaxTime = each_person._job_done_list[-1]['end_time']
            else:
                each_personMaxTime = 0
            if each_personMaxTime >= all_person_max_time:
                all_person_max_time = each_personMaxTime
        # 用设置的最大时间减去所有人中最大的时间来衡量适应度
        fitness_val = all_person_max_time

        return fitness_val

# 工作安排里面的人员实体类，保存人员的工作信息
class PersonEntity:
    def __init__(self, person_info, job_list, work_cost_matrix, transport_cost_matrix):
        self._person_info = person_info
        self._job_list = job_list
        self._work_cost_matrix = work_cost_matrix
        self._transport_cost_matrix = transport_cost_matrix
        self._now_work_start_time = 0
        self._now_work_end_time = 0
        self._now_work_prev_trans_time = 0
        self._now_job_id = -1
        self._job_done_list = []

    # 更新该员工的工作列表，包括任务号的阶段，起始时间
    def updateMachineDoneJobStatus(self, job_id):
        _temp_job_done = {}
        _temp_job_done['job_id'] = job_id
        _temp_job_done['job_name'] = self._job_list[job_id]['job_name']
        _temp_job_done['person_id'] = self._person_info['person_id']
        # 先查出前置任务的id和时间，如果是-1则表示没有前置任务
        prev_job_id = -1
        prev_job_end_time = 0
        prev_job_name = -1
        if len(self._job_done_list) == 0:
            prev_job_id = -1
            prev_job_end_time = 0
            prev_job_name = '公司'
        else:
            prev_job_id = self._job_done_list[-1]['job_id']
            prev_job_end_time = self._job_done_list[-1]['end_time']
            prev_job_name = self._job_done_list[-1]['job_name']
        _temp_job_done['prev_job_id'] = prev_job_id
        _temp_job_done['prev_job_end_time'] = prev_job_end_time
        _temp_job_done['prev_job_name'] = prev_job_name
        # 计算前置任务到现任务的交通时间开销，
        # 如果没有前置任务则交通时间为从公司（编号最后一位即最后一行）起点开始的_transport_cost_matrix的时间
        if prev_job_id == -1:
            transport_cost_time = self._transport_cost_matrix[-1][job_id]
            _temp_job_done['transport_cost_time'] = transport_cost_time
        else:
            # 如果有前置任务则交通时间为从起点开始的_transport_cost_matrix的时间
            transport_cost_time = self._transport_cost_matrix[prev_job_id][job_id]
            _temp_job_done['transport_cost_time'] = transport_cost_time
        # 计算当前任务的开始时间，为前置任务结束时间加上交通时间
        if prev_job_id == -1:
            start_time = transport_cost_time
        else:
            start_time = prev_job_end_time + transport_cost_time
        _temp_job_done['start_time'] = start_time
        # 工时信息表中查出任务的时间开销
        work_cost_time = self._work_cost_matrix[self._person_info['person_id']][job_id]
        _temp_job_done['work_cost_time'] = work_cost_time
        _temp_job_done['end_time'] = start_time + work_cost_time
        # 将工作记录移入列表
        self._job_done_list.append(_temp_job_done)
        # 将当前开工时间，当前任务前置交通时间和当前任务结束时间更新，便于查找
        self._now_work_start_time = _temp_job_done['start_time']
        self._now_work_end_time = _temp_job_done['end_time']
        self._now_work_prev_trans_time = _temp_job_done['transport_cost_time']
        self._now_job_id = _temp_job_done['job_id']


# 工作安排里的工作实体类，保存工作的信息
class JobEntity:
    def __init__(self, job_info, work_cost_matrix, transport_cost_matrix):
        self._job_info = job_info
        # self._work_cost_matrix = work_cost_matrix
        # self._transport_cost_matrix = transport_cost_matrix
        # self._now_job_start_time = 0
        # self._now_job_end_time = 0
        # self._now_job_prev_trans_time = 0


# 数据初始化，4人员，12任务版本
def base_complex_data_init():
    # 人员信息
    person_list = [
        {'person_id': 0, 'person_name': '1号员工'},
        {'person_id': 1, 'person_name': '2号员工'},
        {'person_id': 2, 'person_name': '3号员工'},
        {'person_id': 3, 'person_name': '4号员工'},
    ]
    # 任务信息
    job_list = [
        {'job_id': 0, 'job_name': '1号任务'},
        {'job_id': 1, 'job_name': '2号任务'},
        {'job_id': 2, 'job_name': '3号任务'},
        {'job_id': 3, 'job_name': '4号任务'},
        {'job_id': 4, 'job_name': '5号任务'},
        {'job_id': 5, 'job_name': '6号任务'},
        {'job_id': 6, 'job_name': '7号任务'},
        {'job_id': 7, 'job_name': '8号任务'},
        {'job_id': 8, 'job_name': '9号任务'},
        {'job_id': 9, 'job_name': '10号任务'},
        {'job_id': 10, 'job_name': '11号任务'},
        {'job_id': 11, 'job_name': '12号任务'},
    ]
    # 工时信息，用二维数组表示,表示每个人完成任务所需要的时间，行数为人员号，列为任务号，
    # 比如第一行，第四列表示1号员工（person_id为0）完成4号任务（job_id为3）的时间
    work_cost_matrix = [
        [4, 2, 3, 4, 2, 4, 2, 3, 4, 2, 4, 2],
        [4, 4, 2, 2, 2, 4, 4, 2, 2, 2, 4, 4],
        [4, 3, 1, 1, 2, 4, 3, 1, 1, 2, 4, 3],
        [4, 3, 1, 1, 1, 4, 3, 1, 1, 1, 4, 3],
    ]
    # 固定生成13*13的交通开销矩阵，默认第一行为第一个地点到各个地点的时间开销，
    # 最后一行为公司到各个地点的开销，这样保证序号都是对的上
    # 为便于调试，指定种子值使得该矩阵永远是这个样子
    np.random.seed(1234)
    transport_cost_matrix = np.random.randint(1, 10, (12 + 1, 12 + 1))
    np.random.seed(None)

    # 工作的安排顺序
    work_assign_sequence = [1, 0, 2, 3, 5, 6, 4, 12, 10, 9, 8, 11, 7]

    return person_list, job_list, work_cost_matrix, transport_cost_matrix, work_assign_sequence


# 数据初始化，3人员，8任务版本
def base_data_init():
    # 人员信息
    person_list = [
        {'person_id': 0, 'person_name': '1号员工'},
        {'person_id': 1, 'person_name': '2号员工'},
        {'person_id': 2, 'person_name': '3号员工'}
    ]
    # 任务信息
    job_list = [
        {'job_id': 0, 'job_name': '1号任务'},
        {'job_id': 1, 'job_name': '2号任务'},
        {'job_id': 2, 'job_name': '3号任务'},
        {'job_id': 3, 'job_name': '4号任务'},
        {'job_id': 4, 'job_name': '5号任务'},
        {'job_id': 5, 'job_name': '6号任务'},
        {'job_id': 6, 'job_name': '7号任务'},
        {'job_id': 7, 'job_name': '8号任务'},
    ]
    # 工时信息，用二维数组表示,表示每个人完成任务所需要的时间，行数为人员号，列为任务号，
    # 比如第一行，第四列表示1号员工（person_id为0）完成4号任务（job_id为3）的时间
    work_cost_matrix = [
        [4, 2, 3, 4, 2, 4, 2, 3],
        [4, 4, 2, 2, 2, 4, 4, 2],
        [4, 3, 1, 1, 2, 4, 3, 1],
    ]
    # 固定生成9*9的交通开销矩阵，默认第一行为第一个地点到各个地点的时间开销，
    # 最后一行为公司到各个地点的开销，这样保证序号都是对的上
    # 为便于调试，指定种子值使得该矩阵永远是这个样子
    np.random.seed(1234)
    transport_cost_matrix = np.random.randint(1, 10, (8 + 1, 8 + 1))
    np.random.seed(None)

    # 工作的安排顺序
    work_assign_sequence = [1, 0, 2, 3, 5, 6, 4, 7]

    return person_list, job_list, work_cost_matrix, transport_cost_matrix, work_assign_sequence


if __name__ == '__main__':
    person_list, job_list, work_cost_matrix, transport_cost_matrix, work_assign_sequence = base_data_init()
    workAssignment = WorkAssignment(person_list, job_list, work_cost_matrix, transport_cost_matrix,
                                    work_assign_sequence)
    workAssignment.workAssign(work_assign_sequence)
    workAssignment.drawGantt()
    pass
